﻿/*
 * Twipler - Twitter Web Client with features.
 *  
 *  Copyright (C) 2009, 2010. Ian Quigley
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details. 
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;

using Twipler.App.Machine;

namespace Twipler.App.Parse.Code
{
    public class CodeParse : StringParser
    {
        public CodeParse(string content) : base(content) { }
        public CodeParse(StringParser parser) : base(parser) { }

        public static CodeParse SwitchFrom(StringParser parser)
        {
            return new CodeParse(parser);
        }

        public List<Instruction> ReadInstructions()
        {
            List<Instruction> program = new List<Instruction>();
            Dictionary<string, int> labels = new Dictionary<string, int>();

            while (!IsEmpty)
            {
                SkipWhiteSpace();

                if (IsEmpty)
                    break;

                // Find next mnemonic instruction.
                string mnemonic = NextWord();

                // begins with ':' - so is a label
                if (mnemonic[0] == ':')
                {
                    labels.Add(mnemonic.Substring(1), program.Count());
                    continue;
                }
                
                // Mnemonic not recognised
                if (!Instructions.Exists(mnemonic))
                {
                    // Is it a mnemonic followed by some junk?
                    string junk = Instructions.FollowedByJunk(mnemonic);

                    Rewind(junk);

                    // All junk
                    if (mnemonic.Length == junk.Length)
                        break;

                    // Get the none-junk part
                    mnemonic = mnemonic.Substring(0, mnemonic.Length - junk.Length);
                }
                
                // Find instruction's constructor parameters
                Type instructionType = Instructions.GetType(mnemonic);
                ConstructorInfo[] constructors = instructionType.GetConstructors();
                
                // Is this a branch instruction?
                if (IsBranchInstruction(instructionType))
                {
                    // Branch instructions can only have 1 constructor
                    if (constructors.Count() != 1)
                    {
                        AddError("Invalid mnemonic " + mnemonic + ". Branch Instructions can only have 1 constructor.");
                        return null;
                    }
                        
                    program.Add((Instruction)constructors[0].Invoke(new object[] { NextWord(), program.Count}));
                    continue;
                }

                // One constructor with no parameters
                if (OneConstructorWithNoParameters(constructors))
                {
                    program.Add((Instruction)constructors[0].Invoke(new object[] {}));
                    continue;
                }

                // This shouldn't happen
                if (MultipleConstructorsOneWithNoParameters(constructors))
                {
                    AddError("Invalid mnemonic " + mnemonic + ". Cannot have multiple constructors where one has no parameters.");
                    return null;
                }


                // Match parameters to a constructor.
                List<object> readParameters = new List<object>();
                TokenParser tokenParser = new TokenParser(this);
                IEnumerable<ConstructorInfo> candidates = constructors.AsEnumerable();
                
                // Expect param, param, param....
                while (candidates.Count() > 0 && !tokenParser.IsEmpty && !tokenParser.HasErrors)
                {
                    TokenParser.Result result = tokenParser.Next(TokenParser.TokenType.Any);

                    if (result is TokenParser.ResultError)
                    {
                        AddError(((TokenParser.ResultError)result).Message);
                        return null;
                    }

                    if (result is TokenParser.ResultEmpty)
                        break;

                    readParameters.Add(result.Value);
                	int readParameterCount = readParameters.Count();

                    // Reduce candidates to ones whose parameter[index] is assignable from this
                    candidates = from candidate in candidates
								 where 
									candidate.GetParameters().Count() >= readParameterCount 
								 && (    IsReferenceSetToNull(candidate, readParameterCount, result)
								      || IsTypeAssignableFromValue(candidate, readParameterCount, result) )
                                 select candidate;

                    // Only one candidate, and we have the right number of parameters
                    if ((candidates.Count() == 1) && (candidates.First().GetParameters().Count() == readParameters.Count()))
                        break;
                    
                    if (!tokenParser.SkipChar(','))
                        AddError("Expected , seperating parameters");
                }

                Digest(tokenParser);

                if (HasErrors)
                    return null;
                
                if (candidates.Count() == 0)
                {
                    AddError("Could not find match for " + mnemonic + " with those parameters.");
                    return null;
                }

                if (candidates.Count() > 1)
                {
                    AddError("More than one match for " + mnemonic + " with those parameters.");
                    return null;
                }

                // Create using constructor
                object inst = candidates.First().Invoke((object[])readParameters.ToArray());
                program.Add((Instruction)inst);
            }
            
            PostParse_CreateBranchDeltas(labels, program);
            
            return program;
        }

    	private bool IsTypeAssignableFromValue(ConstructorInfo candidate, int readParameterCount, TokenParser.Result result)
    	{
    		return candidate.GetParameters()[readParameterCount - 1].ParameterType.IsAssignableFrom(result.Value.GetType());
    	}

    	private bool IsReferenceSetToNull(ConstructorInfo candidate, int readParameterCount, TokenParser.Result result)
    	{
    		return (!candidate.GetParameters()[readParameterCount - 1].ParameterType.IsValueType && result.Value == null);
    	}

    	private void PostParse_CreateBranchDeltas(Dictionary<string, int> labels, List<Instruction> program)
        {
            foreach (Instruction inst in program)
            {
                if (!typeof(BranchInstruction).IsAssignableFrom(inst.GetType()))
                    continue;

                string lblName = ((BranchInstruction)inst).Name;

                if (!labels.Keys.Contains(lblName))
                {
                    AddError("Undefined label " + lblName);
                    continue;
                }

                int index = labels[lblName];
                ((BranchInstruction)inst).Delta = index - ((BranchInstruction)inst).Delta;
            }
        }

        private bool IsBranchInstruction(Type instructionType)
        {
            return typeof (BranchInstruction).IsAssignableFrom(instructionType);
        }

        private bool OneConstructorWithNoParameters(ConstructorInfo[] constuctors)
        {
            return ((constuctors.Count() == 1) && (constuctors[0].GetParameters().Count() == 0));
        }

        private bool MultipleConstructorsOneWithNoParameters(ConstructorInfo[] constuctors)
        {
            if (constuctors.Count() == 1)
                return false;

            foreach (ConstructorInfo ci in constuctors)
                if (ci.GetParameters().Count() == 0)
                    return true;

            return false;
        }
    }
}